import DEFAULTS from './defaults.js'  // 默认参数
import {serialize,addUrlData,serializeJSON} from './utils.js';    // 工具函数
import {
    HTTP_GET,
    CONTENT_TYPE_JSON,
    CONTENT_TYPE_FORM_URLENCONDED
} from './constants.js';

// Ajax 类
class Ajax{
    constructor(url,options){
        this.url = url;
        this.options = Object.assign({},DEFAULTS,options);

        // 初始化
        this.init();
    }

    // 初始化函数
    init(){
        const xhr = new XMLHttpRequest();
        this.xhr = xhr;

        // 绑定事件处理函数
        this.bindEvents();

        xhr.open(this.options.method,this.url + this.addParam() ,true);

        // 设置请求头类型
        this.setResposeseType();

        // 设置跨域是否携带 cookie
        this.setCookie();

        // 设置超时
        this.setTimeout();

        // 发送请求
        this.sendData();
    }

    // 绑定事件处理回调
    bindEvents(){
        const xhr = this.xhr;
        const {success,httpCodeError,error,abort,timeout} = this.options;

        // 监听响应事件 -- load 事件
        xhr.addEventListener('load',()=>{
            if(this.ok()){
                success(xhr.response,xhr);
            }else{
                httpCodeError(xhr.status,xhr);
            }
        })

        // 请求错误事件 -- error 事件
        xhr.addEventListener('error',()=>{
            error(xhr);
        })

        // 请求终止事件 -- abort 事件
        xhr.addEventListener('abort',()=>{
            abort(xhr);
        })

        // 请求超时事件 -- timeout 事件
        xhr.addEventListener('timeout',()=>{
            timeout(xhr);
        })
    }
    // 判断 http 状态码
    ok(){
        const xhr = this.xhr;

        return (xhr.status >= 200 && xhr.status < 300) || xhr.status === 304;
    }

    // url 上添加数据
    addParam(){
        const {params} = this.options;

        if(!params) return '';

        return addUrlData(this.url,serialize(params));  // 数据序列化
    }

    // 设置请求头数据类型的回调
    setResposeseType(){
        this.xhr.responseType = this.options.responseType;
    }

    // 设置跨域是否携带 cookie 的回调
    setCookie(){
        if(this.options.withCredentials){
            this.xhr.withCredentials = this.options
        }
    };

    // 设置超时的回调
    setTimeout(){
        const {timeoutTime} = this.options;

        if(timeoutTime > 0){
            this.xhr.timeout = timeoutTime;
        }
    };

    // 发送请求的回调
    sendData(){
        const xhr = this.xhr;

        if(this.options.method.toLowerCase === HTTP_GET || this.options.data === null){
            return xhr.send(null);
        }

        let resultData = null;  // 发送的数据
        const {data} = this.options
        // 判断发送的数据格式
        // 1. FormData 格式
        if(this.isFormData){
            resultData = data;
        }else if(this.isFormURLEncodedData){  // 2. 键值对格式
            this.setContentType(CONTENT_TYPE_FORM_URLENCONDED);  // 设置 ContentType
            resultData = serialize(data);
        }else if(this.isJSONData){  // 3. JSON 格式

            this.setContentType(CONTENT_TYPE_JSON);  // 设置 ContentType
            resultData = serializeJSON(data);
        }else{  // 4. 其他数据格式

            this.setContentType();  // 设置 ContentType
            resultData = data;
        }

        xhr.send(resultData);
    };

    // 判断发送的数据格式 -- 1. FormData 格式 的回调
    isFormData(){
        return this.options.data instanceof FormData;
    }

    // 判断发送的数据格式 -- 2. 键值对格式
    isFormURLEncodedData(){
        return this.options.contentType.toLowerCase().includes(CONTENT_TYPE_FORM_URLENCONDED);
    }

    // 判断发送的数据格式 -- 3. JSON 格式
    isJSONData(){
        return this.options.contentType.toLowerCase().includes(CONTENT_TYPE_JSON);
    }

    // 设置 ContentType
    setContentType(contentType = this.options.contentType){
        if(!contentType) return;
        this.xhr.setContentType('Content-Type',contentType)
    }

    //获取 xhr 对象
    getXHR(){
        return this.xhr;
    }
}

export default Ajax;


/*
    参数：
        默认参数：DEFAULTS
        options：用户传的参数
*/

/* 
    Object.assign() 方法将用于对象之间的合并
    语法： Object.assign(target, source1, source2)
    object.assign方法的第一个参数是目标(多个对象中的属性都保存到第一个里面)对象，后面的参数都是源对象 
    相同参数：后面的参数会覆盖前面的参数
*/
/*
    判断实例:
        xxx instanceof xxx
*/
// includes() 方法用来判断一个数组是否包含一个指定的值，根据情况，如果包含则返回 true，否则返回 false。